# = Class ASTStatement
# Inherits from AST. Represents the implementation of an AST especifically designed
# for statements of the programming language Vectorinox
# == Attributes inherited from AST
# node, children
# == Methods inherited from AST
# check(), run()
class ASTStatement < AST

  attr_reader :type
  attr_reader :returned
  
  # Constructor of the class
  # === Parameters
  # * node: Token that represents the identifier for the operation represented by the AST
  # * children: AST of all the elements of the operation
  def initialize node, children
    @node = node
    @children = children
    #for c in @children
    #  puts "hijo #{c.class.name} de statement #{@node.to_s}"
    #end
  end

  # Method to run the statement
  # === Parameters
  # * parent_sym_table: Symbol table passed by the parent AST
  # * func_table: Function symbol table passed by the parent AST
  # === Return value
  # nil if executed with success or an error message
  def run parent_sym_table, func_table

    @returned = nil
    case @node.value
    when 'write'
      output = ''
      for c in @children
        msg = c.eval parent_sym_table, func_table
        if nil != msg
          return msg
        end
        case c.type
        when 'String', 'num', 'mat', 'vec_fil', 'vec_col'
          output += c.value.to_s
        when 'Booleano'
          output += c.value ? 'true' : 'false'
        end
      end
      puts output + "\n"
    when 'read'
      # Array.ASTVar.Array.TkVar
      sym_var = parent_sym_table.find @children[0].names[0].value
      case sym_var.type[0,3]
      when 'num'
        sym_var.value = nil
        while nil == sym_var.value
          sym_var.value = $stdin.gets.to_i
        end
      when 'vec'
        length = 0
        while 0 == length
          puts "Introduzca el tamaño del vector: "
          length = $stdin.gets
          length = length.to_i
        end
        v = []
        for i in (1..length)
          puts "Introduzca vec[#{i}]:"
          v[i - 1] = $stdin.gets.to_i
        end
        sym_var.value = Vector.new v
        sym_var.type = 'vec_fil'
      when 'mat'
        # number of rows
        height = 0
        while 0 == height
          puts "Introduzca el número de filas de la matriz: "
          height = $stdin.gets.to_i
        end
        # number of cols
        width = 0
        while 0 == width
          puts "Introduzca el ancho de filas de la matriz: "
          width = $stdin.gets.to_i
        end
        # data
        lvec = []
        for i in (1..height)
          v = []
          for j in (1..width)
            puts "Introduzca mat[#{i}][#{j}]:"
            v[j - 1] = $stdin.gets.to_i 
          end
          vec = Vector.new v
          lvec.push vec
        end
        sym_var.value = Matrix.new lvec
      end
    when 'return'
      msg = @children[0].eval parent_sym_table, func_table
      if nil != msg
        return msg
      end
      @returned = @children[0].value
    when 'while'
      while true
        # test while condition
        msg = @children[0].eval parent_sym_table, func_table
        if nil != msg
          return msg
        end
        # while condition stopped being true
        break if !@children[0].value
        # execute internal instruction
        msg = @children[1].run parent_sym_table, func_table
        if nil != msg
          return msg
        end
        # a return statement was performed inside while
        break if nil != @children[1].returned
      end
    when 'if'
      # test while condition
      msg = @children[0].eval parent_sym_table, func_table
      if nil != msg
        return msg
      end
      if @children[0].value
        msg = @children[1].run parent_sym_table, func_table
        if nil != msg
          return msg
        end
        if nil != @children[1].returned
          @returned = @children[1].returned
        end
      else
        if nil != @children[2]
          msg = @children[2].run parent_sym_table, func_table
          if nil != msg
            return msg
          end
          if nil != @children[2].returned
            @returned = @children[2].returned
          end
        end
      end
    when ':='
      # get assignable component
      msg = @children[0].eval parent_sym_table, func_table
      if nil != msg
        return msg
      end
      # calculate assigned expression
      msg = @children[1].eval parent_sym_table, func_table
      if nil != msg
        return msg
      end
      sym_var = parent_sym_table.find @children[0].var.value
      if nil == @children[0].range
        if 'mat' == sym_var.type
          sym_var.value = @children[1].value.matrix
        elsif 'num' == sym_var.type
          sym_var.value = @children[1].value
        else
          # This is a vector
          sym_var.value = @children[1].value
          sym_var.type = @children[1].type
        end
      else
        msg = sym_var.value.assign @children[0].range, @children[1].value
        if nil != msg
          return msg + " (#{error_location})"
        end
      end
    when 'foreach'
      # Get expression to iterate over
      msg = @children[1].eval parent_sym_table, func_table
      if nil != msg
        return msg
      end
      # Array.ASTVar.Array.TkVar
      name = @children[0].names[0].value
      # Create no-modifiable variable in this scope
      sym_var = SymVariable.new name, 'mat' == @children[1].type ? 'vec_fil' : 'num'
      sym_var.modifiable = false
      parent_sym_table.insert(sym_var)

      # Foreach. It's over Matrix or Vector. Both have a 'value' which is an array
      for val in @children[1].value.value
        sym_var.value = 'mat' == @children[1].type ? Vector.new(val) : val
        if val.kind_of?Vector
          sym_var.value.type = sym_var.type = 'vec_fil'
        else
          sym_var.type = 'num'
        end
        msg = @children[2].run parent_sym_table, func_table
        if nil != msg
          # This variable is only set in this environment
          parent_sym_table.delete(sym_var.name)
          return msg
        end
        # Return statement executed during foreach
        break if nil != @children[2].returned
      end
      # This variable is only set in this environment
      parent_sym_table.delete(sym_var.name)
    end
    nil
  end

  # Method to check the static correctitude of the statement
  # === Parameters
  # * parent_sym_table: Symbol table passed by the parent AST
  # * func_table: Function symbol table passed by the pare
  # * func_type: Type of valid return value
  # === Return value
  # nil if executed with success or an error message
  def check parent_sym_table, func_table, func_type
    @type = 'statement'
    if nil == @node
      return "Error interno: Instrucción sin tipo (#{error_location})"
    end
    if nil == @children
      return "Error interno: Instrucción indefinida (#{error_location})"
    end
    for c in @children
      # They require special treatment for variable in foreach
      next if 'foreach' == @node.value
      msg = c.check parent_sym_table, func_table, func_type
      if nil != msg
        return msg
      end
    end

    case @node.value
    when 'read'
      # Array.ASTVar.Array.TkVar
      var_name = @children[0].names[0].value
      sym_var = parent_sym_table.find var_name
      if nil == sym_var
        return "Tratando de leer variable '#{var_name}' sin declarar (#{error_location})"
      end
      if !sym_var.modifiable
        return "La variable '#{var_name}' no es modificable (#{error_location})"
      end
    when 'return'
      if nil == func_type
        return "Error interno: No existe la función que se llamó (#{error_location})"
      end
      if @children[0].type != func_type
        return "Tratando de devolver '#{@children[0].type}' en función " + 
               " de tipo '#{func_type}' (#{error_location})"
      end
    when 'while', 'if'
      if !@children[0].kind_of?ASTBool
        return "Tratando de ejecutar un while/if sin condición booleana (#{@children[0].error_location})"
      end
    when ':='
      if 'mat' == @children[0].type
        if 'vec' != @children[1].type && 'mat' != @children[1].type
          return "Tratando de asignar '#{@children[1].type}' a '#{@children[0].type}' (#{error_location})"
        end
      elsif 'vec' == @children[0].type
        if 'vec' != @children[1].type
          return "Tratando de asignar '#{@children[1].type}' a '#{@children[0].type}' (#{error_location})"
        end
      elsif 'num' == @children[0].type
        if 'num' != @children[1].type
          return "Tratando de asignar '#{@children[1].type}' a '#{@children[0].type}' (#{error_location})"
        end
      end
    when 'foreach'
      msg = @children[0].check parent_sym_table, func_table, func_type
      if nil != msg
        return msg
      end

      # Check expression about foreach
      msg = @children[1].check parent_sym_table, func_table, func_type
      if nil != msg
        return msg
      end

      # Array.ASTVar.Array.TkVar
      name = @children[0].names[0].value
      if nil != parent_sym_table.find(name)
        return "Tratando de iterar usando variable '#{name}' ya declarada (#{@children[0].error_location})"
      end

     # Create no-modifiable variable in this scope
      sym_var = SymVariable.new name, 'mat' == @children[1].type ? 'vec' : 'num'
      sym_var.modifiable = false
      parent_sym_table.insert(sym_var)

      # Check instructions comming
      msg = @children[2].check parent_sym_table, func_table, func_type

      # This variable is only set in this environment
      parent_sym_table.delete(sym_var.name)

      if nil != msg
        return msg
      end

      if 'mat' == @children[1].type
        # We should be assigning vec_fil, matrix elements for iteration are rows
        @children[0].type = 'vec'
      end
      if 'vec' == @children[1].type
        @children[0].type = 'num'
      end
      if nil == @children[0].type
        return "Tratando de iterar sobre '#{@children[1].type}' (#{@children[1].error_location})"
      end
    end
    nil
  end

  # Returns the closest wrong token to the place were an error is generated
  def error_tk
    if nil != @node
      return @node
    end
    for i in @children
      tk = i.error_tk
      if nil != tk
        return tk
      end
    end
    nil
  end

  # Creates a 'String' representation of the AST with 'i' identation spaces
  # === Parameters
  # * i: Identation with which the AST should be printed with
  def to_s i
    im = i + @@c
    output = "(#{@node.class.name}"
    if @children == nil
      return "#{output}\n#{im}#{@node.value}\n#{i})"
    end
    output += to_s_children(im) + "\n#{i})"
  end
  
  # Creates a 'String' representation of all the children of the AST
  # === Parameters
  # * im: Identation with which the AST should be printed with
  def to_s_children im
    output = ''
    i = 0
    for c in @children
      if nil != c
        output += "\n#{im}" + c.to_s(im)
        i += 1
      end
    end
    output
  end
end